package demo5;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class BitManipulation {
    static int maxLength = 32;

    public DrawLine createDrawLine() {
        return new DrawLine();
    }

    public NextNum createNextNum() {
        return new NextNum();
    }

    public OverTurnBitPos1 createOverTurnBitPos1() {
        return new OverTurnBitPos1();
    }

    public OverTurnBitPos createOverTurnBitPos() {
        return new OverTurnBitPos();
    }

    public Insert createInsert(int n) {
        return new Insert(n);
    }

    public Transform createTransform() {
        return new Transform();
    }

    public class Demo {
        public boolean getBit(int num, int i) {
            return (num & (1 << i)) != 0;
        }

        public int setBit(int num, int i) {
            return num | (1 << i);
        }

        public int clearBit(int num, int i) {
            int mask = ~(1 << i);
            return num & mask;
        }

        /**
         * 01001 10000
         * 00000 10000
         * 100000
         * 11111
         */
        public int clearBitsMSBthroughI(int num, int i) {
            int mask = (1 << i) - 1;
            return num & mask;
        }

        /**
         * 01001 10000
         * 11111
         * i
         * -1:   1111
         */
        public int clearBitsIthrough0(int num, int i) {
            int mask = -1 << (i + 1);
            return num & mask;
        }

        /**
         * 01001 10000 i=2
         * 01001 10100 i=2
         * 11111111011
         * <p>
         * 01001 10000
         * 00000000100 or 00000000000
         */
        public int updateBit(int num, int i, boolean bitIs1) {
            int n = bitIs1 ? 1 : 0;
            int mask = ~(1 << i);
            return (num & mask) | (n << i);
        }
    }

    @AllArgsConstructor
    @NoArgsConstructor
    @Setter
    @Getter
    public class Insert {
        private int n;

        /**
         * insert
         * M:      100 11             i=2,j=6
         * N: 100 0000 0000
         * newN:  100 0100 1100
         * 0100 0000
         * ~ (NOT)
         * ^ (XOR)
         * when  M: 10011       i=2,j=6
         * N: 100 0100 0000
         */
        public Integer insert(int m, int start, int end) {
            StringBuilder stringBuilder = new StringBuilder(Integer.toBinaryString(m) + "");
            for (int i = 0; i < start; i++) {
                stringBuilder.append("0");
            }
            Integer newM = Integer.valueOf(stringBuilder.toString(), 2);

            // if the seq is straight;
            int mask1 = (1 << (start)) - 1;
            int mask2 = -1 << (end + 1);
            int special1 = n & mask1; // first two bit
            int special2 = n & mask2; // last 25 bit
            int newVal = special1 | newM | special2;
            System.out.println("the initial val is " + Integer.toBinaryString(n) + ",the val is : " + Integer.toBinaryString(newVal) + " after insert m ");
            return newVal;
        }

    }
//    public class

    public class Transform {
        public String printBinary(double num) {
            if (num >= 1 || num <= 0) {
                return "ERROR";
            }

            StringBuilder binary = new StringBuilder();
            binary.append(".");
            while (num > 0) {
                /* Setting a limit on length: 32 characters */
                if (binary.length() > 32) {
                    return "ERROR";
                }
                double r = num * 2;
                if (r >= 1) {
                    binary.append(1);
                    num = r - 1;
                } else {
                    binary.append(0);
                    num = r;
                }
            }
            return binary.toString();
        }

        /**
         * input : 29  0000 1101
         * output: 15  0011 1111
         */
        public int transformInteger(int currentNum, int newNum) {
            int[] ints = new int[32];
            int counter = 0;
            int j = currentNum ^ newNum;
            // get 1 num ,then 32- j
            while (j != 0) {
                if ((j & 1) == 1) {
                    counter++;
                }
                j = j >> 1;
            }
            return counter;
        }

        /**
         * 0000 1101 => 1+4+8=13
         * <p>
         * 1110 0111
         * 10 vs 5
         * x
         * 1010 1010 vs 0101 0101
         * <p>
         * <p>
         * (1000)>>>1  => 0100
         * (0100)<<1  => 1000
         * <p>
         * 0100 | 1000 =1100
         */
        public String swapEvenAndOdd(int num) {
// need to use the  while loop
            // if the even and odd val is equal ,then don't swap
            Integer beforeBit = null;
            int[] ints = new int[31];
            int i = 31; // 0-30
            while (num != 0) {
                if (beforeBit == null) {
                    beforeBit = num & 1;

                } else {
                    ints[--i] = num & 1;
                    ints[--i] = beforeBit;
                    // clear double ,or silbings
                    beforeBit = null;
                }
                num >>= 1;
            }
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j < ints.length; j++) {
                sb.append(ints[j]);
            }
            return sb.toString();
        }

        public int swapEvenAndOdd2(int num) {
            int i = ((num & 0xaaaaaaaa) >>> 1) | ((num & 0x55555555) << 1);
            return i;
        }

    }
    //max       0110 0110
    //goal->    0101 0101
    //min       0011 0011

    /**
     * max      1001  9   when first 1  is sibling to  the second 1 ,the first 1 need to << and the second  1 need is  the most low .
     * goal --> 0110  6
     * min      0101  5   , just change the second 1 >>
     *
     * <p>
     * max      0110  6   just change the  the second 1 <<
     * goal --> 0101  5
     * min      0011  3 when the second 1 is the most low ,
     * need to change the first 1  , >> .and the second 1 should locate in the sibling position.
     * <p>
     * max            11 0110 1000 1111
     * goal -->       11 0110 0111 1100
     * min            11 0110 0111 1010
     * <p>
     * max            11 0110 1111 1110
     * goal -->       11 0110 0100 1100
     * 11 0000 0000 0000
     * 10 0000 0000 0000
     * 01 0000 0000 0000
     * 00 0000 0000 0000
     * 00 0000 0000 0100
     * <p>
     * min            11 0110 0111 1111
     *
     * <p>
     * max            11 0110 1111 0000   update the most low 1 pos  to 0 ,then  update the the left pos  0 begin from  (most low 1 pos) to 1
     * goal -->       0001
     */
    public class NextNum {
        public int getPrevSlow(int num) {
            if (!validPrevSlow(num)) {
                return -1;
            }
            int countOnes = countOnes(num);
            num = num - 1;
            while (countOnes != countOnes(num)) {
                num = num - 1;
            }
            return num;
        }

        private int countOnes(int num) {
            int count = 0;
            while (num != 0) {
                if ((num & 1) == 1) {
                    count++;
                }
                num = num >> 1;
            }
            return count;
        }

        private boolean validPrevSlow(int num) {
            if (num == 0 || num == 1) {
                return false;
            }
            while ((num & 1) == 1) {
                num = num >> 1;
            }
            if (num == 0) {
                return false;
            }
            return true;
        }

        public int getNextSlow(int num) {
            if (!validNextSlow(num)) {
                return -1;
            }
            int countOnes = countOnes(num);
            num++;
            while (countOnes != countOnes(num)) {
                num++; // num
            }
            return num;

        }

        private boolean validNextSlow(int num) {
            if (num == 0) {
                return false;
            }
            int count = 0;

            while ((num & 1) == 0) {
                num = num >> 1;
                count++;
            }
            while ((num & 1) == 1) {
                num = num >> 1;
                count++;
            }
            if (count == 31) {
                return false;
            }
            return true;
        }

        public void findMostSimilarNumber(int num) {
            int prev = getPrevSlow(num);
            int next = getNextSlow(num);
            System.out.println("the current is : " + Integer.toBinaryString(num) + ",the prev is :" + Integer.toBinaryString(prev) + " , the next is : " + Integer.toBinaryString(next));

        }

        private int updateBit(int num, int i) {
            return num | (1 << i);
        }

        private int clearBit(int num, int i) {
            return ~(1 << i) & num;
        }

        ;

        private boolean getBit(int num, int i) {
            return (num & (1 << i)) != 0;
        }
    }

    public class OverTurnBitPos1 {

        public int findMostLengthSequence(int num) {
            int max = 0;
            for (int i = 0; i < maxLength; i++) {
                max = Math.max(max, findMostLengthSequenceOf1s(num, i));

            }
            return max;
        }

        public int findMostLengthSequenceOf1s(int num, int indexToIgnore) {
            int counter = 0, max = 0;
            for (int i = 0; i < maxLength; i++) {
                if (i == indexToIgnore || getBit(num, i)) {
                    counter++;
                    max = Math.max(max, counter);
                } else {
                    counter = 0;
                }
            }
            return max;
        }

        private boolean getBit(int num, int i) {
            return (num & (1 << i)) != 0;
        }
    }

    public class OverTurnBitPos {
        @NoArgsConstructor
        @AllArgsConstructor
        @Getter
        @Setter
        public class Result implements Comparable {
            private Integer key;
            private Integer val;

            @Override
            public int compareTo(Object o) {
                if (o instanceof Map.Entry) {
                    Map.Entry<Integer, Integer> o1 = (Map.Entry<Integer, Integer>) o;
                    if (this.val - o1.getValue() > 0) {
                        return 1;
                    } else if (this.val - o1.getValue() == 0) {
                        return 0;
                    } else {
                        return -1;
                    }
                } else {
                    throw new RuntimeException("type incompotial");
                }
            }
        }

        /**
         * 1775 11011101111
         * <p>
         * 110011110111011
         * 1 2 3  4  56 7
         * get currently the most 1 position   ,locate in the start or end ,
         * 111001100001
         * map only contains the  quantity of 1 and position
         * map1 only contains the  quantity of 0  and position
         */
        public int findMostLengthSequence(int num) {
            // 0x7fffff
            // 01111111
            String s = Integer.toBinaryString(Integer.valueOf(num));
            return findMostLengthSequence(s);
        }

        public int findMostLengthSequence(String s) {
            HashMap<Integer, Integer> mapContaionsZero = new HashMap<>();
            HashMap<Integer, Integer> mapContaionsOne = new HashMap<>();
            boolean zeroflag = false;
            boolean oneflag = false;
            int zeroCount = 0, oneCount = 0;
            int part = 0;
            char[] chars = s.toCharArray();
            // -1
            // 111111
            for (int i = 0; i < chars.length; i++) {
                if (chars[i] == '1') {
                    if (!oneflag) {
                        oneflag = true;
                        part++;
                        mapContaionsOne.compute(part, (k, v) -> {
                            return 0;
                        });
                    }

                    mapContaionsOne.compute(part, (k, v) -> {
                        return ++v;
                    });

                    zeroflag = false;
                    zeroCount = 0;
                } else {
                    if (!zeroflag) {
                        zeroflag = true;
                        part++;
                        mapContaionsZero.compute(part, (k, v) -> {
                            return 0;
                        });
                    }
                    mapContaionsZero.compute(part, (k, v) -> {
                        return ++v;
                    });
                    oneflag = false;
                    oneCount = 0;
                }

            }
            return Sum(mapContaionsZero, mapContaionsOne);

        }

        private int Sum(HashMap<Integer, Integer> mapContaionsZero, HashMap<Integer, Integer> mapContaionsOne) {
            /**
             *  1775 11011101111
             * {2=1, 4=1} zero
             * {1=2, 3=3, 5=4} one
             */
            System.out.println(mapContaionsZero);
            System.out.println(mapContaionsOne);
            int max = 0;
            Set<Map.Entry<Integer, Integer>> entries = mapContaionsOne.entrySet();
            for (Map.Entry<Integer, Integer> entry : entries) {
                Integer value = entry.getValue();

                Integer integer1 = mapContaionsZero.get(entry.getKey() - 1);
                if (integer1 != null) {
                    if (integer1 == 1) {
                        Integer integer = mapContaionsOne.get(entry.getKey() - 2);
                        int i = value + (integer == null ? 0 : integer) + 1;
                        if (i > max) {
                            max = i;
                        }
                    } else {
                        int i = value + 1;
                        if (i > max) {
                            max = i;
                        }
                    }
                }
                Integer integer2 = mapContaionsZero.get(entry.getKey() + 1);
                if (integer2 != null) {
                    if (integer2 == 1) {
                        Integer integer = mapContaionsOne.get(entry.getKey() + 2);
                        int i = value + (integer == null ? 0 : integer) + 1;
                        if (i > max) {
                            max = i;
                        }
                    } else {
                        int i = value + 1;
                        if (i > max) {
                            max = i;
                        }
                    }
                }

            }
            return max;


        }

    }

    public class DrawLine {
        /**
         *
         */
        public void drawLineSimple(byte[] screen, int width, int x1, int x2, int y) {
            int byteEveryLineInPossession = width / 8; // 4
            int high = screen.length / byteEveryLineInPossession; // 3
            // make Two-dimensional array then get val
            byte[][] bytes = new byte[high][byteEveryLineInPossession];
            for (int i = 0, j = 0, z = 0; i < screen.length; i++, j++) {
                if (j == byteEveryLineInPossession) {
                    j = 0;
                    bytes[++z][j] = screen[i];
                } else {
                    bytes[z][j] = screen[i];
                }
            }
            byte[] newLine = new byte[x2 - x1 + 1];

            // for
            for (int i = x1, j = 0; i <= x2; i++, j++) {
                newLine[j] = bytes[y][i];
            }

            System.out.println("-----------------draw the new Line-------------------");
            for (int i = 0; i < newLine.length; i++) {
                System.out.print(newLine[i] + ",");
            }
// 0 -> 1
//
        }

        /**
         * only could to change the screen
         * x1: 10 ,x2: 18
         * x1: 3
         * 8
         * x1/8   => int 0 => 3
         *                                                       * x1 / 8 => int 1 => 2  6
         * x2 / 8 => int 2 => 2  3
         */
        public void drawLine(byte[] screen, int width, int x1, int x2, int y) {
            // get x1 and x2
            int byteEveryLineInPossession = width / 8; // 4
            int high = screen.length / byteEveryLineInPossession; // 3
            // the pos from x1 -> x2
            // 不同字节下，或者相同字节下
            int startSegment = (int) x1 / 8;
            int endSegment = (int) x2 / 8;
            int startPosInY = byteEveryLineInPossession * (high - 1);
            // 10 , 15
            for (int i = startSegment; i <= endSegment; i++) {
                screen[startPosInY + i] = settingPixel(i, x1, x2);
            }
            printLine(screen,startPosInY);

        }
        private void printLine(byte[] screen, int startPosInY){
            // 0,63,-64,0
            //   01111111 11000000
            System.out.println("----------------draw line----------------------------");
            for (int i = startPosInY; i < screen.length; i++) {
                 System.out.print(screen[i]+",");
            }
        }
        private byte settingPixel(int currentPos, int x1, int x2) {
            // 3,5
            int startSegment = (int) x1 / 8; // 2
            int endSegment = (int) x2 / 8; // 2
            if(currentPos == startSegment && currentPos == endSegment){
                int skipPos = x1 % 8; // 3
                int includePos = (x2 % 8); // 5
                int first = ((1 << (8 - skipPos + 1))-1);
                int second =  (-1 << (8 - includePos));
                return (byte)(first & second);
            }
            if (currentPos == startSegment) {
                int skipPos = x1 % 8;
                return (byte) ((1 << (8 - skipPos + 1))-1);
            }
            if (currentPos == endSegment) {
                int includePos = (x2 % 8); // 18-16=2;
                return (byte) (-1 << (8 - includePos)); // clear zero
            }

            return -1;
        }
    }

}
